'use strict';

const path = require('path');
const childProcess = require('child_process');

const pkgDir = require('pkg-dir');
const inquirer = require('inquirer');
const Conf = require('conf');
const pathExists = require('path-exists').sync;
const fsExtra = require('fs-extra');
const ora = require('ora');
const glob = require('glob');
const colors = require('colors');

const log = require('@x3-cli-dev/log');
const Command = require('@x3-cli-dev/command');
const { getProjectList } = require('@x3-cli-dev/request');
const { execCommandAsync, formatCommand, arrGroup, WORKSPACE_CONFIG_NAME } = require('@x3-cli-dev/utils');

const { CONF_SCHEMA, CONF_SCHEMA_U } = require('./schema');
const { unlinkSync } = require('fs');

class SvmCommand extends Command {
  get isDebug() {
    return process.env.LOG_LEVEL === 'verbose';
  }

  init() {
    this.useEnvironment = this._options.use;
    this.message = this._options.message ?? '';
    this.force = !!this._options.force;
    this.application = this._options.application;
    log.verbose('useEnvironment', this.useEnvironment);
    log.verbose('message', this.message);
    log.verbose('force', this.force);
    log.verbose('application', this.application);
  }

  async exec() {
    try {
      const projectInfo = await this.prepare();
      this.projectInfo = projectInfo;
      log.verbose('projectInfo', this.projectInfo);
      await this.svnCommand();
    } catch (error) {
      log.error(error.message);
      if (this.isDebug) {
        console.log(error);
      }
    } finally {
      process.exit(0);
    }
  }

  async prepare() {
    await this.checkConfirm();
    await this.checkConf();
    return await this.getProjectInfo();
  }

  async checkConfirm() {
    if (this.force) return;
    const { ifContinue } = await inquirer.prompt({
      name: 'ifContinue',
      type: 'confirm',
      message: '确认提交 SVN 吗? ',
      default: false,
    });
    if (!ifContinue) {
      process.exit(0);
    }
  }

  async addConf(config, key = '') {
    let {svnRootPath = {}, defaultSvnRootPath} = config.get();
    const needSet = defaultSvnRootPath ? CONF_SCHEMA_U : CONF_SCHEMA;
    const confObj = await inquirer.prompt(
      needSet.map(item => ({
        name: item.key,
        message: item.message,
        type: item.type,
        default: item.default,
      })),
    );
    svnRootPath[confObj.svnRootPathName || key] = confObj.svnRootPathValue;
    config.set('svnRootPath', svnRootPath);
    if (!defaultSvnRootPath){
      config.set('defaultSvnRootPath', confObj.svnRootPathName);
    }
    log.verbose('config', config.get());
    // this.confStore = config;
  }

  async checkConf() {
    const cliPath = process.env.X3_CLI_HOME_PATH;
    const config = new Conf({
      cwd: cliPath,
    });
    let {svnRootPath = {}, defaultSvnRootPath} = config.get();
    log.verbose('config', config.get());
    if (!Object.keys(svnRootPath).length) {
      await this.addConf(config)
    }

    if (this.useEnvironment) {
      if (svnRootPath.hasOwnProperty(this.useEnvironment)) {
        defaultSvnRootPath = this.useEnvironment;
      } else {
        log.warn(`${colors.yellow('找不到指定svn根目录配置 '+this.useEnvironment)}`);
        await this.addConf(config, this.useEnvironment)
        // throw new Error(
        //   `找不到指定svn根目录配置 ${colors.blue(this.useEnvironment)}, 请使用 ${colors.yellow(
        //     'x3 config --list',
        //   )} 查看当前服务器配置`,
        // );
      }
    }
    if (!defaultSvnRootPath) {
      defaultSvnRootPath = Object.keys(svnRootPath)[0];
      log.warn(`${colors.yellow('找不到默认svn根目录配置，将默认使用第一个svn根目录配置发布项目')}`);
    }
    this.defaultSvnRootPath = svnRootPath[defaultSvnRootPath];
    log.info(`svn配置名称 ${colors.blue(defaultSvnRootPath)}`);
    log.info(`svn更新路径 ${colors.blue(this.defaultSvnRootPath)}`);
  }

  async getProjectInfo() {
    const workspaceConfigPath = path.join(process.cwd(), WORKSPACE_CONFIG_NAME);
    // 是否使用本地工作空间配置
    if (fsExtra.pathExistsSync(workspaceConfigPath)) {
      this.projectList = require(workspaceConfigPath);
    } else {
      this.projectList = await getProjectList();
    }
    if (!this.projectList || this.projectList.length <= 0) {
      throw new Error('不存在线上项目列表');
    }
    // 判断是否指定应用名
    const packageDir = await pkgDir(process.cwd());
    if (!this.application) {
      const packagePath = path.resolve(packageDir, 'package.json');
      this.application = require(packagePath).name;
    }

    const project = this.projectList.find(project => project.packageName === this.application || project.alias === this.application);

    if (!project) {
      log.verbose('', this.projectList);
      throw new Error(`线上配置不存在 ${pkg.name} 项目配置`);
    }

    let selectProject = project;
    // 是否多项目发布
    if (Array.isArray(project.children) && project.children.length) {
      log.verbose('project.children', project.children);

      selectProject = (
        await inquirer.prompt({
          name: 'project',
          type: 'list',
          choices: project.children.map(item => ({
            name: item.name,
            value: item,
          })),
          message: '请选择发布项目',
        })
      ).project;
    }

    log.verbose('selectProject', selectProject);
    const outputPath = path.join(packageDir, selectProject.outputPath);

    // 检查 SVN 路径
    // const { svnRootPath, defaultSvnRootPath } = this.confStore.get();
    // if (!this.useEnvironment && !defaultSvnRootPath) {
    //   throw new Error(`svnRootPath 不存在，请检查配置!`);
    // }
    const svnFullPath = path.join(this.defaultSvnRootPath, selectProject.svnPath);
    if (!pathExists(svnFullPath)) {
      throw new Error(`svn 路径不存在，请检查配置! svnPath: ${svnFullPath}`);
    }

    return {
      ...selectProject,
      packageDir,
      outputPath,
      svnFullPath,
    };
  }

  async svnCommand() {
    const spinner = ora('更新 SVN').start();
    try {
      await this.svnUpdate();
      spinner.succeed('更新 SVN 成功').start('清空 SVN 目录');
      await this.svnClear();
      spinner.succeed('清空 SVN 目录成功').start('拷贝发布文件到 SVN');
      await this.svnCopy();
      spinner.succeed('拷贝发布文件到 SVN 成功').start('新文件添加到版本控制');
      await this.svnAdd();
      spinner.succeed('新文件添加到版本控制成功').start('删除已版本控制文件');
      await this.svnDelete();
      spinner.succeed('删除已版本控制文件成功').start('提交 SVN');
      await this.svnCommit();
      spinner.succeed('提交 SVN 成功');
    } catch (error) {
      spinner.fail('svn 提交失败');
      throw error;
    }
  }

  async svnUpdate() {
    return await execCommandAsync('svn update', this.getSvnCommandOptions());
  }

  async svnClear() {
    const { includeProject } = this.projectInfo;
    // 项目目录内包含项目模式，使用特殊的删除逻辑
    if (includeProject) {
      return new Promise((resolve, reject) => {
        glob('*', { cwd: this.projectInfo.svnFullPath, nodir: true }, (error, files) => {
          if (error) reject(error);
          for (const file of files) {
            const filePath = path.join(this.projectInfo.svnFullPath, file);
            unlinkSync(filePath);
          }
          const assetsPath = path.join(this.projectInfo.svnFullPath, 'assets');
          fsExtra.emptyDirSync(assetsPath);
          resolve(files);
        });
      });
    } else {
      await fsExtra.emptyDir(this.projectInfo.svnFullPath);
    }
  }

  async svnCopy() {
    const { outputPath, svnFullPath } = this.projectInfo;
    await fsExtra.copy(outputPath, svnFullPath);
  }

  async svnAdd() {
    return await execCommandAsync('svn add . --force', this.getSvnCommandOptions());
  }

  async svnDelete() {
    const { cmdAndArgs } = formatCommand('svn', ['status']);
    return new Promise((resolve, reject) => {
      childProcess.exec(cmdAndArgs, this.getSvnCommandOptions(), async (error, stdout) => {
        if (error) {
          reject(error);
        }
        if (stdout) {
          const tempList = stdout
            .toString()
            .split('\n')
            .filter(str => str.startsWith('!'))
            .map(str => str.replace(/^! */, '').trim());
          const sliceArr = arrGroup(tempList, 30);
          for (const arr of sliceArr) {
            const str = arr.reduce((pre, cur) => `${pre ? pre : ''} "${cur}"`, '').trim();
            if (str) {
              const { cmdAndArgs: rmCmd } = formatCommand('svn', ['rm', str, '--force']);
              log.verbose('svn rm command', rmCmd);
              childProcess.execSync(rmCmd, this.getSvnCommandOptions());
            }
          }
        }
        resolve(stdout);
      });
    });
  }

  async svnCommit() {
    const { cmdAndArgs } = formatCommand('svn', ['commit', '-m', `"${this.message}"`]);
    log.verbose('svn commit', cmdAndArgs);
    return new Promise((resolve, reject) => {
      childProcess.exec(cmdAndArgs, this.getSvnCommandOptions(), (error, stdio, stderr) => {
        if (error) {
          reject(error);
        } else {
          resolve(stdio.toString());
        }
      });
    });
  }

  getSvnCommandOptions() {
    const { svnFullPath } = this.projectInfo;
    return {
      cwd: svnFullPath,
      stdio: this.isDebug ? 'inherit' : 'ignore',
      encoding: 'utf-8'
    };
  }
}

function svn(argv) {
  return new SvmCommand(argv);
}

module.exports = svn;
module.exports.SvmCommand = SvmCommand;
